home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / singleton.rb < prev    next >
Text File  |  2008-07-03  |  8KB  |  360 lines

  1. # The Singleton module implements the Singleton pattern.
  2. #
  3. # Usage:
  4. #    class Klass
  5. #       include Singleton
  6. #       # ...
  7. #    end
  8. #
  9. # *  this ensures that only one instance of Klass lets call it
  10. #    ``the instance'' can be created.
  11. #
  12. #    a,b  = Klass.instance, Klass.instance
  13. #    a == b   # => true
  14. #    a.new    #  NoMethodError - new is private ...
  15. #
  16. # *  ``The instance'' is created at instantiation time, in other
  17. #    words the first call of Klass.instance(), thus
  18. #
  19. #      class OtherKlass
  20. #        include Singleton
  21. #        # ...
  22. #      end
  23. #      ObjectSpace.each_object(OtherKlass){} # => 0.
  24. #
  25. # *  This behavior is preserved under inheritance and cloning.
  26. #
  27. #
  28. #
  29. # This is achieved by marking
  30. # *  Klass.new and Klass.allocate - as private
  31. #
  32. # Providing (or modifying) the class methods
  33. # *  Klass.inherited(sub_klass) and Klass.clone()  -
  34. #    to ensure that the Singleton pattern is properly
  35. #    inherited and cloned.
  36. #
  37. # *  Klass.instance()  -  returning ``the instance''. After a
  38. #    successful self modifying (normally the first) call the
  39. #    method body is a simple:
  40. #
  41. #       def Klass.instance()
  42. #         return @__instance__
  43. #       end
  44. #
  45. # *  Klass._load(str)  -  calling Klass.instance()
  46. #
  47. # *  Klass._instantiate?()  -  returning ``the instance'' or
  48. #    nil. This hook method puts a second (or nth) thread calling
  49. #    Klass.instance() on a waiting loop. The return value
  50. #    signifies the successful completion or premature termination
  51. #    of the first, or more generally, current "instantiation thread".
  52. #
  53. #
  54. # The instance method of Singleton are
  55. # * clone and dup - raising TypeErrors to prevent cloning or duping
  56. #
  57. # *  _dump(depth) - returning the empty string.  Marshalling strips
  58. #    by default all state information, e.g. instance variables and
  59. #    taint state, from ``the instance''.  Providing custom _load(str)
  60. #    and _dump(depth) hooks allows the (partially) resurrections of
  61. #    a previous state of ``the instance''.
  62.  
  63.  
  64.  
  65. module Singleton
  66.   #  disable build-in copying methods
  67.   def clone
  68.     raise TypeError, "can't clone instance of singleton #{self.class}"
  69.   end
  70.   def dup
  71.     raise TypeError, "can't dup instance of singleton #{self.class}"
  72.   end
  73.  
  74.   #  default marshalling strategy
  75.   def _dump(depth=-1)
  76.     ''
  77.   end
  78. end
  79.  
  80.  
  81. class << Singleton
  82.   #  Method body of first instance call.
  83.   FirstInstanceCall = proc do
  84.     #  @__instance__ takes on one of the following values
  85.     #  * nil     -  before and after a failed creation
  86.     #  * false  -  during creation
  87.     #  * sub_class instance  -  after a successful creation
  88.     #  the form makes up for the lack of returns in progs
  89.     Thread.critical = true
  90.     if  @__instance__.nil?
  91.       @__instance__  = false
  92.       Thread.critical = false
  93.       begin
  94.         @__instance__ = new
  95.       ensure
  96.         if @__instance__
  97.           class <<self
  98.             remove_method :instance
  99.             def instance; @__instance__ end
  100.           end
  101.         else
  102.           @__instance__ = nil #  failed instance creation
  103.         end
  104.       end
  105.     elsif  _instantiate?()
  106.       Thread.critical = false
  107.     else
  108.       @__instance__  = false
  109.       Thread.critical = false
  110.       begin
  111.         @__instance__ = new
  112.       ensure
  113.         if @__instance__
  114.           class <<self
  115.             remove_method :instance
  116.             def instance; @__instance__ end
  117.           end
  118.         else
  119.           @__instance__ = nil
  120.         end
  121.       end
  122.     end
  123.     @__instance__
  124.   end
  125.  
  126.   module SingletonClassMethods
  127.     # properly clone the Singleton pattern - did you know
  128.     # that duping doesn't copy class methods?
  129.     def clone
  130.       Singleton.__init__(super)
  131.     end
  132.  
  133.     def _load(str)
  134.       instance
  135.     end
  136.  
  137.     private
  138.  
  139.     #  ensure that the Singleton pattern is properly inherited
  140.     def inherited(sub_klass)
  141.       super
  142.       Singleton.__init__(sub_klass)
  143.     end
  144.  
  145.     # waiting-loop hook
  146.     def _instantiate?()
  147.       while false.equal?(@__instance__)
  148.         Thread.critical = false
  149.         sleep(0.08)   # timeout
  150.         Thread.critical = true
  151.       end
  152.       @__instance__
  153.     end
  154.   end
  155.  
  156.   def __init__(klass)
  157.     klass.instance_eval { @__instance__ = nil }
  158.     class << klass
  159.       define_method(:instance,FirstInstanceCall)
  160.     end
  161.     klass
  162.   end
  163.  
  164.   private
  165.   #  extending an object with Singleton is a bad idea
  166.   undef_method :extend_object
  167.  
  168.   def append_features(mod)
  169.     #  help out people counting on transitive mixins
  170.     unless mod.instance_of?(Class)
  171.       raise TypeError, "Inclusion of the OO-Singleton module in module #{mod}"
  172.     end
  173.     super
  174.   end
  175.  
  176.   def included(klass)
  177.     super
  178.     klass.private_class_method  :new, :allocate
  179.     klass.extend SingletonClassMethods
  180.     Singleton.__init__(klass)
  181.   end
  182. end
  183.  
  184.  
  185.  
  186. if __FILE__ == $0
  187.  
  188. def num_of_instances(klass)
  189.     "#{ObjectSpace.each_object(klass){}} #{klass} instance(s)"
  190. end
  191.  
  192. # The basic and most important example.
  193.  
  194. class SomeSingletonClass
  195.   include Singleton
  196. end
  197. puts "There are #{num_of_instances(SomeSingletonClass)}"
  198.  
  199. a = SomeSingletonClass.instance
  200. b = SomeSingletonClass.instance # a and b are same object
  201. puts "basic test is #{a == b}"
  202.  
  203. begin
  204.   SomeSingletonClass.new
  205. rescue  NoMethodError => mes
  206.   puts mes
  207. end
  208.  
  209.  
  210.  
  211. puts "\nThreaded example with exception and customized #_instantiate?() hook"; p
  212. Thread.abort_on_exception = false
  213.  
  214. class Ups < SomeSingletonClass
  215.   def initialize
  216.     self.class.__sleep
  217.     puts "initialize called by thread ##{Thread.current[:i]}"
  218.   end
  219. end
  220.  
  221. class << Ups
  222.   def _instantiate?
  223.     @enter.push Thread.current[:i]
  224.     while false.equal?(@__instance__)
  225.       Thread.critical = false
  226.       sleep 0.08
  227.       Thread.critical = true
  228.     end
  229.     @leave.push Thread.current[:i]
  230.     @__instance__
  231.   end
  232.  
  233.   def __sleep
  234.     sleep(rand(0.08))
  235.   end
  236.  
  237.   def new
  238.     begin
  239.       __sleep
  240.       raise  "boom - thread ##{Thread.current[:i]} failed to create instance"
  241.     ensure
  242.       # simple flip-flop
  243.       class << self
  244.         remove_method :new
  245.       end
  246.     end
  247.   end
  248.  
  249.   def instantiate_all
  250.     @enter = []
  251.     @leave = []
  252.     1.upto(9) {|i|
  253.       Thread.new {
  254.         begin
  255.           Thread.current[:i] = i
  256.           __sleep
  257.           instance
  258.         rescue RuntimeError => mes
  259.           puts mes
  260.         end
  261.       }
  262.     }
  263.     puts "Before there were #{num_of_instances(self)}"
  264.     sleep 3
  265.     puts "Now there is #{num_of_instances(self)}"
  266.     puts "#{@enter.join '; '} was the order of threads entering the waiting loop"
  267.     puts "#{@leave.join '; '} was the order of threads leaving the waiting loop"
  268.   end
  269. end
  270.  
  271.  
  272. Ups.instantiate_all
  273. # results in message like
  274. # Before there were 0 Ups instance(s)
  275. # boom - thread #6 failed to create instance
  276. # initialize called by thread #3
  277. # Now there is 1 Ups instance(s)
  278. # 3; 2; 1; 8; 4; 7; 5 was the order of threads entering the waiting loop
  279. # 3; 2; 1; 7; 4; 8; 5 was the order of threads leaving the waiting loop
  280.  
  281.  
  282. puts "\nLets see if class level cloning really works"
  283. Yup = Ups.clone
  284. def Yup.new
  285.   begin
  286.     __sleep
  287.     raise  "boom - thread ##{Thread.current[:i]} failed to create instance"
  288.   ensure
  289.     # simple flip-flop
  290.     class << self
  291.       remove_method :new
  292.     end
  293.   end
  294. end
  295. Yup.instantiate_all
  296.  
  297.  
  298. puts "\n\n","Customized marshalling"
  299. class A
  300.   include Singleton
  301.   attr_accessor :persist, :die
  302.   def _dump(depth)
  303.     # this strips the @die information from the instance
  304.     Marshal.dump(@persist,depth)
  305.   end
  306. end
  307.  
  308. def A._load(str)
  309.   instance.persist = Marshal.load(str)
  310.   instance
  311. end
  312.  
  313. a = A.instance
  314. a.persist = ["persist"]
  315. a.die = "die"
  316. a.taint
  317.  
  318. stored_state = Marshal.dump(a)
  319. # change state
  320. a.persist = nil
  321. a.die = nil
  322. b = Marshal.load(stored_state)
  323. p a == b  #  => true
  324. p a.persist  #  => ["persist"]
  325. p a.die      #  => nil
  326.  
  327.  
  328. puts "\n\nSingleton with overridden default #inherited() hook"
  329. class Up
  330. end
  331. def Up.inherited(sub_klass)
  332.   puts "#{sub_klass} subclasses #{self}"
  333. end
  334.  
  335.  
  336. class Middle < Up
  337.   include Singleton
  338. end
  339.  
  340. class Down < Middle; end
  341.  
  342. puts  "and basic \"Down test\" is #{Down.instance == Down.instance}\n
  343. Various exceptions"
  344.  
  345. begin
  346.   module AModule
  347.     include Singleton
  348.   end
  349. rescue TypeError => mes
  350.   puts mes  #=> Inclusion of the OO-Singleton module in module AModule
  351. end
  352.  
  353. begin
  354.   'aString'.extend Singleton
  355. rescue NoMethodError => mes
  356.   puts mes  #=> undefined method `extend_object' for Singleton:Module
  357. end
  358.  
  359. end
  360.